/***************************************************************************
 *                                                                         *
 *   This file is part of the CrystalMem project,                          *
 *       http://code.google.com/p/crystalmem                               *
 *                                                                         *
 *   Copyright (C) 2008 by Alberto Scarpa <scarpa.alberto@gmail.com>       *
 *   Copyright (C) 2008 by Enrico Ros <enrico.ros@gmail.com>               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "TreeBuilder.h"
#include "MemoryTree.h"
#include <QtCore/QTimer>

// #define RANDOM_MEM_SIZE

TreeBuilder::TreeBuilder( MemoryTree * memoryTree, quint16 listenPort, QObject * parent )
    : QObject( parent )
    , m_memTree( memoryTree )
{
    // create a listening socket (NOTE: THIS IS ONLY CONCEPT CODE!)
    QUdpSocket * socket = new QUdpSocket( this );
    socket->bind( QHostAddress::LocalHost, listenPort );
    connect( socket, SIGNAL(readyRead()), this, SLOT(slotDatagramReceived()) );


//     loadFromFile();
//     m_memTree->printTree();
}

TreeBuilder::~TreeBuilder()
{
    m_allocatedAddresses.clear();
}

#include <QFile>

quint32 read( const char *buf, quint8 *n )
{
    memcpy( n, buf, sizeof(*n) );
    return sizeof(*n);
}

quint32 read( const char *buf, quint32 *n )
{
    memcpy( n, buf, sizeof(*n) );
    return sizeof(*n);
}

quint32 readEntry( const char *buf, MemoryTree::Backtrace &backtrace )
{
    quint32 obj_u32, fcn_u32, ret_u32;
    quint32 idx = 0;
    idx += read( buf + idx, &obj_u32 );
    idx += read( buf + idx, &fcn_u32 );
    idx += read( buf + idx, &ret_u32 );
//     qDebug( "obj_u32: %8x, fcn_u32: %8x, ret_u32: %8x;", obj_u32, fcn_u32, ret_u32 );
//     qDebug( "obj_u32: %li, fcn_u32: %li, ret_u32: %li;", obj_u32, fcn_u32, ret_u32 );
    backtrace.push_front( ret_u32 );
    return idx;
}

quint32 readNew( const char *buf, MemoryTree::Backtrace &backtrace, quint32 &size, quint32 &allocatedAddr )
{
    quint32 idx = 0;
    quint8 separator;
    idx += read( buf + idx, &size );
    idx += read( buf + idx, &allocatedAddr );
//     qWarning( "size: %i %08i", size, allocatedAddr );
    while ( 1 ) {
        idx += read( buf + idx, &separator );
        if ( separator == (quint8)('.') )
            return idx;
        else if ( separator != (quint8)(';') ) {
            qWarning( "readNew: format error: invalid separator" );
            return idx; //FIXME: AL: CHECK THIS
        }
        idx += readEntry( buf + idx, backtrace );
    }
    return idx;
}

quint32 readDelete( const char *buf, quint32 &addr )
{
    quint32 idx = 0;
    idx += read( buf + idx, &addr );
    quint8 separator;
    idx += read( buf + idx, &separator );
    if ( separator != (quint8)('.') ) {
        qWarning( "readDelete: invalid separator" );
    }
    return idx;
}

quint32 TreeBuilder::readBacktrace( const char *buf )
{
    quint32 idx = 0;
    quint8 newDelete;
    idx += read( buf + idx, &newDelete );
    if ( newDelete == 1 || newDelete == 2 ) {
        MemoryTree::Backtrace backtrace;
        quint32 size;
        quint32 allocatedAddr;
        idx += readNew( buf + idx, backtrace, size, allocatedAddr );
        // update tree
#ifdef RANDOM_MEM_SIZE
        int mem = qrand() % 1000;
        size = mem * mem;
#endif
        ProgramNode *n = m_memTree->memoryAdded( backtrace, size );
        AllocationEntry *aEntry = new AllocationEntry;
        aEntry->node = n;
        aEntry->size = size;
        m_allocatedAddresses.insert( allocatedAddr, aEntry );
    } else if ( newDelete >= 3 && newDelete <= 6 ) {
        quint32 addr;
        idx += readDelete( buf + idx, addr );
        if ( !m_allocatedAddresses.contains( addr ) ) {
            qWarning( "TreeBuilder::readBacktrace: delete not allocated mem %8x", addr );
            return idx;
        }
        AllocationEntry *aEntry = m_allocatedAddresses.value( addr );
        aEntry->node->memoryRemoved( aEntry->size, 0 );
        aEntry->node->localMem -= aEntry->size;
        delete aEntry;
        m_allocatedAddresses.remove( addr );
    } else {
        qDebug( "TreeBuilder::readBacktrace: unknow %i", newDelete );
        return idx;
    }
    return idx;
}

bool TreeBuilder::loadFromFile(const QString & fileName)
{
    QFile f( fileName );
    if ( !f.open( QIODevice::ReadOnly ) ) {
        qWarning( "Non posso aprire il file" );
        return false;
    }
    QByteArray ba = f.readAll();
    const char *buf = ba.data();
    int size = ba.size();
    f.close();

    qint32 idx = 0;
    while ( idx < size ) {
        idx += readBacktrace( buf + idx );
    }

    return true;
}

void TreeBuilder::slotDatagramReceived()
{
    // retrieve the socket
    QUdpSocket * socket = dynamic_cast<QUdpSocket *>( sender() );
    if ( !socket )
        return;

    while ( socket->hasPendingDatagrams() ) {

        // read data from socket
        QByteArray datagram;
        datagram.resize( socket->pendingDatagramSize() );
        socket->readDatagram( datagram.data(), datagram.size() );

        // build tree from the data
        readBacktrace( datagram.data() );

    }

    emit treeUpdated();
}
